using System;
using System.IO;

namespace libssk
{
	public class StegFile : IStegFile
	{
		public StegFile()
		{
		}
		
		public StegFile(IStegFile stegFile)
		{
			this.FilePath = stegFile.FilePath;
		}
		
		public string FilePath { get; set; }
		
		/// <summary>
		/// Unioning two files can visually be summed up by "zipping" them together
		/// and using specified block sizes and offsets to insert one file into another
		/// ending with one file containing both files with seemingly random data
		/// </summary>
		/// <param name="toHide">
		/// A <see cref="IStegFile"/>. The file to hide.
		/// </param>
		/// <param name="offset">
		/// A <see cref="System.Int32"/> The byte offset for blocks
		/// </param>
		/// <param name="blocksize">
		/// A <see cref="System.Int32"/>. How many bytes from each file will be in a block?
		/// </param>
		/// <param name="endkey">
		/// A <see cref="System.String"/>. I need to know when the file being inserted will end.
		/// </param>
		/// <returns>
		/// A <see cref="System.Int32"/>
		/// </returns>
		public int Union(IStegFile toHide, int offset, int blocksize, byte[] endkey)
		{
			Console.WriteLine("Unioning...");
			if (endkey.Length != blocksize)
			{
				//end key needs to be the size of a block
				return 1;
			}
			
			string parentPath = this.FilePath;
			string childPath = toHide.FilePath;
			
			FileStream parentStream = File.OpenRead(parentPath);
			FileStream childStream = File.OpenRead(childPath);
			
			if ((childStream.Length%blocksize) > 0)
				return 1;
			
			byte[] parentData = new byte[parentStream.Length];
			byte[] childData = new byte[childStream.Length];
			
			parentStream.Read(parentData, 0, parentData.Length);
			childStream.Read(childData, 0, childData.Length);
			
			if (endkey.Length == 0)
			{
				endkey = GenerateEndKey(blocksize);
			}
			
			return 0; 
		}
		
		/// <summary>
		/// Deunioning two files takes a file that has been Union()'ed and separates them
		/// using the offset, block size, and end key used when Union()'ed
		/// </summary>
		/// <param name="offset">
		/// A <see cref="System.Int32"/>
		/// </param>
		/// <param name="blocksize">
		/// A <see cref="System.Int32"/>
		/// </param>
		/// <param name="endkey">
		/// A <see cref="System.String"/>
		/// </param>
		/// <returns>
		/// A <see cref="System.Int32"/>
		/// </returns>
		public int Deunion(int offset, int blocksize, byte[] endkey)
		{	
			Console.WriteLine("Deunioning...");
			if (endkey.Length != blocksize)
			{
				//end key needs to be the size of a block
				return 1;
			} 
			
			return 0;
		}
		
		/// <summary>
		/// Add a whole file into a specific location (offset) of another file, using the end key
		/// as the marker for the end of the hidden file.
		/// </summary>
		/// <param name="toHide">
		/// A <see cref="IStegFile"/>
		/// </param>
		/// <param name="offset">
		/// A <see cref="System.Int32"/>
		/// </param>
		/// <param name="endkey">
		/// A <see cref="System.String"/>
		/// </param>
		/// <returns>
		/// A <see cref="System.Int32"/>
		/// </returns>
		public int Add(IStegFile toHide, int offset, byte[] endkey)
		{
			Console.WriteLine("Adding...");
			string parentPath = this.FilePath;
			string childPath = toHide.FilePath;
			
			FileStream parentStream = File.OpenRead(parentPath);
			FileStream childStream = File.OpenRead(childPath);
			
			byte[] parentData = new byte[parentStream.Length];
			byte[] childData = new byte[childStream.Length];
			
			parentStream.Read(parentData, 0, parentData.Length);
			childStream.Read(childData, 0, childData.Length);
			
			if (endkey.Length == 0)
			{
				endkey = GenerateEndKey(128);
			}
			
			return 0; 
		}
		
		/// <summary>
		/// Remove a file from another file at offset, using endkey as the marker for the end of the file.
		/// </summary>
		/// <param name="offset">
		/// A <see cref="System.Int32"/>
		/// </param>
		/// <param name="endkey">
		/// A <see cref="System.String"/>
		/// </param>
		/// <returns>
		/// A <see cref="System.Int32"/>
		/// </returns>
		public int Remove(int offset, byte[] endkey)
		{ 
			Console.WriteLine("Removing...");return 0; }
		
		/// <summary>
		/// Obfuscate the file header using provided data
		/// </summary>
		/// <param name="data">
		/// A <see cref="System.String"/>
		/// </param>
		/// <returns>
		/// A <see cref="System.Int32"/>
		/// </returns>
		public int ObfuscateHeader(byte[] data, int offset)
		{ 
			Console.WriteLine("Obfuscating...");return 0; }
		
		/// <summary>
		/// Deobfuscate the file header using the provided data
		/// </summary>
		/// <param name="data">
		/// A <see cref="System.String"/>
		/// </param>
		/// <returns>
		/// A <see cref="System.Int32"/>
		/// </returns>
		public int DeobfuscateHeader(byte[] data, int offset)
		{ 
			Console.WriteLine("Deobfuscating...");return 0; }
		
		/// <summary>
		/// Prepend a fake file header onto the file with the original header after the offset
		/// </summary>
		/// <param name="header">
		/// A <see cref="System.Byte[]"/>
		/// </param>
		/// <param name="offset">
		/// A <see cref="System.Int32"/>
		/// </param>
		/// <returns>
		/// A <see cref="System.Int32"/>
		/// </returns>
		public int FakeHeader(int headerID, int offset)
		{ 
			Console.WriteLine("Faking...");return 0; }
		
		/// <summary>
		/// Remove the fake file header.
		/// </summary>
		/// <param name="offset">
		/// A <see cref="System.Int32"/>. It is the number of bytes from beginning to real file header.
		/// </param>
		/// <returns>
		/// A <see cref="System.Int32"/>
		/// </returns>
		public int DefakeHeader(int offset)
		{ 
			Console.WriteLine("Defaking...");return 0; }
		
		/// <summary>
		/// Generate an end key for use with adding and unioning files.
		/// </summary>
		/// <param name="blocksize">
		/// A <see cref="System.Int32"/>
		/// </param>
		/// <returns>
		/// A <see cref="System.Byte[]"/>
		/// </returns>
		private byte[] GenerateEndKey(int blocksize)
		{
			byte[] endKey = new byte[blocksize];
			Random r = new Random();
			
			for (int i=0;i<blocksize;i++)
			{
				int ranNum = r.Next();
				
				while (ranNum > (int)0xff || ranNum < (int)0x00)
					ranNum = r.Next();
				
				endKey[i] = (byte)ranNum;
			}
			
			return endKey;
		}
	}
}

